package com.jgp.config;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.jgp.common.utils.JGPUtil;
import com.jgp.sys.props.CachePropBean;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.*;

import javax.annotation.Resource;
import java.net.UnknownHostException;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

/**
 * 项目   gavin-project
 * 作者   loufei
 * 时间   2019-05-02
 */
@Order(400)
@EnableCaching(mode = AdviceMode.ASPECTJ)
@Configuration
@ConditionalOnProperty(prefix = "jgp.cache", name = "enabled", havingValue = "true")
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties({CachePropBean.class})
public class CacheConfiguration{
    
    @Autowired
    private CachePropBean cachePropBean;
    
    public static final String TEL_CODE = "TEL_CODE";
    public static final String CHECK_TEL_CODE_CAN_RESEND = "CHECK_TEL_CODE_CAN_RESEND";
    
    @Resource
    private LettuceConnectionFactory connectionFactory;
    
    @Bean
    public RedisSerializer<Object> redisSerializer() {
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer<Object> serializer =
                new Jackson2JsonRedisSerializer<>(Object.class);
        
        ObjectMapper mapper = new ObjectMapper();
        mapper.setTimeZone(TimeZone.getDefault());
        //MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapper.setDateFormat(JGPUtil.S_DATE_TIME_FORMATTER);
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(JGPUtil.DATE_TIME_FORMATTER));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(JGPUtil.DATE_FORMATTER));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(JGPUtil.TIME_FORMATTER));
        javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(JGPUtil.DATE_TIME_FORMATTER));
        javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(JGPUtil.DATE_FORMATTER));
        javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(JGPUtil.TIME_FORMATTER));
        mapper.registerModule(javaTimeModule);
        
        SimpleModule simpleModule = new SimpleModule();
        mapper.registerModule(simpleModule);
        mapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        
        serializer.setObjectMapper(mapper);
        
        return serializer;
    }
    
    /**
     * 管理缓存
     * @param redisConnectionFactory
     * @return
     */
    @SuppressWarnings("all")
    @Bean
    public CacheManager cacheManager(RedisSerializer<Object> redisSerializer) {
        //通过Spring提供的RedisCacheConfiguration类，构造一个自己的redis配置类，从该配置类中可以设置一些初始化的缓存命名空间
        // 及对应的默认过期时间等属性，再利用RedisCacheManager中的builder.build()的方式生成cacheManager：
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();  // 生成一个默认配置，通过config对象即可对缓存进行自定义配置
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer));
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer));
        String rootNameSpace = cachePropBean.getPrefix().toUpperCase();
        config = config.computePrefixWith(cacheName -> {
            String prefix = "";
            if(StringUtils.isNotBlank(rootNameSpace)){
                prefix += rootNameSpace+":";
            }
            if(StringUtils.isNotBlank(cacheName)){
                prefix += cacheName+":";
            }
            return prefix;
        });
        config = config.entryTtl(Duration.ofDays(1))     // 设置缓存的默认过期时间，也是使用Duration设置
                       .disableCachingNullValues();         // 不缓存空值
        // 设置一个初始化的缓存空间set集合
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        for (GnCacheStyle cacheStyle : GnCacheStyle.values()) {
            configMap.put(cacheStyle.name(),config.entryTtl(Duration.ofSeconds(cacheStyle.getTtl())));
        }
        
        configMap.put(TEL_CODE,config.entryTtl(Duration.ofSeconds(900)));
        configMap.put(CHECK_TEL_CODE_CAN_RESEND,config.entryTtl(Duration.ofSeconds(60)));
        
        // 对每个缓存空间应用不同的配置
        RedisCacheManager cacheManager = RedisCacheManager.builder(connectionFactory)     // 使用自定义的缓存配置初始化一个cacheManager
                                                          .initialCacheNames(configMap.keySet())  // 注意这两句的调用顺序，一定要先调用该方法设置初始化的缓存名，再初始化相关的配置
                                                          .withInitialCacheConfigurations(configMap)
                                                          .cacheDefaults(config)
                                                          .build();
        cacheManager.afterPropertiesSet();
        return cacheManager;
    }
    
    @SuppressWarnings("all")
    @Bean
    @ConditionalOnMissingBean(
            name = {"redisTemplate"}
    )
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory, RedisSerializer<Object> redisSerializer) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(new JdkSerializationRedisSerializer());
        // value序列化方式采用jackson
        template.setHashValueSerializer(redisSerializer);
        template.afterPropertiesSet();
        return template;
    }
    
    
    @SuppressWarnings("all")
    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

